package org.eiahe.hr.attendance.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import io.github.linpeilie.Converter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eiahe.hr.attendance.client.DingTalkApiClient;
import org.eiahe.hr.attendance.config.DingTalkProperties;
import org.eiahe.hr.attendance.domain.*;
import org.eiahe.hr.attendance.domain.vo.SyncLogVo;
import org.eiahe.hr.attendance.mapper.AttendanceRecordMapper;
import org.eiahe.hr.attendance.mapper.LeaveApplicationMapper;
import org.eiahe.hr.attendance.mapper.SyncLogMapper;
import org.eiahe.hr.attendance.service.IDingTalkSyncService;
import org.ehe.common.core.utils.StringUtils;
import org.ehe.system.domain.HrEmployee;
import org.ehe.system.mapper.HrEmployeeMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.*;
import java.util.*;

/**
 * 钉钉考勤同步Service业务层处理
 *
 * @author zhangnn
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class DingTalkSyncServiceImpl implements IDingTalkSyncService {

    private final DingTalkApiClient dingTalkApiClient;
    private final DingTalkProperties dingTalkProperties;
    private final AttendanceRecordMapper attendanceRecordMapper;
    private final LeaveApplicationMapper leaveApplicationMapper;
    private final SyncLogMapper syncLogMapper;
    private final HrEmployeeMapper employeeMapper;
    private final Converter converter;


    /**
     * 同步钉钉考勤数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SyncLogVo syncAttendanceRecords(LocalDate startDate, LocalDate endDate, boolean isManual) {
        log.info("开始同步钉钉考勤数据: {} 到 {}, 是否手动: {}", startDate, endDate, isManual);

        // 检查是否启用钉钉同步
        if (!dingTalkProperties.getEnabled()) {
            log.warn("钉钉考勤同步未启用");
            throw new RuntimeException("钉钉考勤同步未启用");
        }

        // 创建同步日志
        SyncLog syncLog = new SyncLog();
        syncLog.setSyncSource("DINGTALK");
        syncLog.setSyncType(isManual ? "MANUAL" : "AUTO");
        syncLog.setSyncStartTime(LocalDateTime.now());
        syncLog.setSyncStatus("0"); // 进行中
        syncLog.setTotalCount(0);
        syncLog.setSuccessCount(0);
        syncLog.setFailCount(0);
        syncLog.setCreateTime(LocalDateTime.now());
        syncLogMapper.insert(syncLog);

        int totalCount = 0;
        int successCount = 0;
        int failCount = 0;
        StringBuilder failReasons = new StringBuilder();

        try {
            // 1. 获取所有在职员工信息
            List<HrEmployee> employees = employeeMapper.selectList(
                    Wrappers.lambdaQuery(HrEmployee.class)
                            .in(HrEmployee::getEmployeeStatus, "1", "2") // 1-在职, 2-试用
                            .isNotNull(HrEmployee::getMobile));

            if (CollUtil.isEmpty(employees)) {
                log.warn("未找到有效的员工数据");
                updateSyncLog(syncLog, "2", 0, 0, 0, "未找到有效的员工数据");
                return converter.convert(syncLog, SyncLogVo.class);
            }

            log.info("找到{}个员工", employees.size());

            // 2. 构建员工手机号与钉钉UserID的映射
            Map<String, HrEmployee> mobileToEmployeeMap = new HashMap<>();
            List<String> dingTalkUserIds = new ArrayList<>();

            for (HrEmployee employee : employees) {
                if (StringUtils.isNotBlank(employee.getMobile())) {
                    // 通过手机号获取钉钉UserID
                    String dingTalkUserId = dingTalkApiClient.getUserIdByMobile(employee.getMobile());
                    if (StringUtils.isNotBlank(dingTalkUserId)) {
                        dingTalkUserIds.add(dingTalkUserId);
                        mobileToEmployeeMap.put(dingTalkUserId, employee);
                        log.debug("员工{} 手机号{} 对应钉钉UserID: {}",
                                employee.getEmployeeName(), employee.getMobile(), dingTalkUserId);
                    } else {
                        log.warn("员工{} 手机号{} 未找到对应的钉钉UserID",
                                employee.getEmployeeName(), employee.getMobile());
                    }
                }
            }

            if (CollUtil.isEmpty(dingTalkUserIds)) {
                log.warn("未找到任何钉钉用户ID");
                updateSyncLog(syncLog, "2", 0, 0, 0, "未找到任何钉钉用户ID");
                return converter.convert(syncLog, SyncLogVo.class);
            }

            log.info("找到{}个钉钉用户ID", dingTalkUserIds.size());

            // 3. 获取钉钉考勤记录
            List<DingTalkAttendanceRecord> dingTalkRecords = dingTalkApiClient
                    .getAttendanceRecords(dingTalkUserIds, startDate, endDate);

            totalCount = dingTalkRecords.size();
            log.info("从钉钉获取到{}条考勤记录", totalCount);

            // 4. 处理每条考勤记录
            for (DingTalkAttendanceRecord dingTalkRecord : dingTalkRecords) {
                try {
                    HrEmployee employee = mobileToEmployeeMap.get(dingTalkRecord.getUserId());
                    if (employee == null) {
                        failCount++;
                        failReasons.append("钉钉UserID ")
                                .append(dingTalkRecord.getUserId())
                                .append(" 未找到对应员工; ");
                        continue;
                    }

                    // 转换为系统考勤记录
                    AttendanceRecord attendanceRecord = convertToAttendanceRecord(dingTalkRecord, employee);

                    // 检查是否已存在(避免重复)
                    boolean exists = checkRecordExists(attendanceRecord);
                    if (exists) {
                        log.debug("考勤记录已存在,跳过: 员工{}, 打卡时间{}",
                                employee.getEmployeeNo(), attendanceRecord.getClockTime());
                        attendanceRecord.setIsDuplicate("1");
                    } else {
                        attendanceRecord.setIsDuplicate("0");
                    }

                    // 保存考勤记录
                    attendanceRecordMapper.insert(attendanceRecord);
                    successCount++;

                } catch (Exception e) {
                    failCount++;
                    log.error("处理考勤记录失败", e);
                    failReasons.append("处理记录失败: ").append(e.getMessage()).append("; ");
                }
            }

            // 5. 更新同步日志
            String finalStatus = failCount == 0 ? "1" : "2"; // 1-成功, 2-部分失败
            updateSyncLog(syncLog, finalStatus, totalCount, successCount, failCount, failReasons.toString());

            log.info("钉钉考勤数据同步完成: 总数{}, 成功{}, 失败{}", totalCount, successCount, failCount);

        } catch (Exception e) {
            log.error("同步钉钉考勤数据异常", e);
            updateSyncLog(syncLog, "2", totalCount, successCount, failCount,
                    "同步异常: " + e.getMessage());
            throw new RuntimeException("同步钉钉考勤数据失败", e);
        }

        return converter.convert(syncLog, SyncLogVo.class);
    }

    /**
     * 自动同步(定时任务调用)
     */
    @Override
    public void autoSync() {
        // 同步数据
        LocalDate yesterday = LocalDate.now().minusDays(1);
        syncAttendanceRecords(yesterday, LocalDate.now(), false);
    }

    /**
     * 重新同步失败的记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean retrySyncByLogId(Long logId) {
        SyncLog syncLog = syncLogMapper.selectById(logId);
        if (syncLog == null) {
            throw new RuntimeException("同步日志不存在");
        }

        // 只有失败的记录才能重试
        if (!"2".equals(syncLog.getSyncStatus())) {
            throw new RuntimeException("只能重试失败的同步记录");
        }

        // 重新同步(根据原始的同步时间范围)
        LocalDate startDate = syncLog.getSyncStartTime().toLocalDate();
        LocalDate endDate = syncLog.getSyncEndTime() != null ? syncLog.getSyncEndTime().toLocalDate() : startDate;

        try {
            syncAttendanceRecords(startDate, endDate, true);
            return true;
        } catch (Exception e) {
            log.error("重新同步失败", e);
            return false;
        }
    }

    /**
     * 转换钉钉考勤记录为系统考勤记录
     */
    private AttendanceRecord convertToAttendanceRecord(
        DingTalkAttendanceRecord dingTalkRecord, HrEmployee employee) {

        AttendanceRecord record = new AttendanceRecord();
        record.setEmployeeNo(employee.getEmployeeNo());
        record.setUserId(employee.getUserId());

        // 打卡时间
        LocalTime clockTime = dingTalkRecord.getUserCheckTime()
                .toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalTime();
        record.setClockTime(clockTime);
        record.setAttendanceDate(LocalDate.now());

        // 打卡类型: OnDuty上班, OffDuty下班
        if ("OnDuty".equals(dingTalkRecord.getCheckType())) {
            record.setClockType("1"); // 上班
        } else if ("OffDuty".equals(dingTalkRecord.getCheckType())) {
            record.setClockType("2"); // 下班
        }

        // 打卡方式: 钉钉统一为手机APP
        record.setClockMethod("2"); // 手机APP
        record.setDeviceId(dingTalkRecord.getDeviceId());
        record.setLocation(dingTalkRecord.getLocationTitle());

        // 考勤状态: 根据时间结果和位置结果判断
        String attendanceStatus = "0"; // 默认正常
        StringBuilder exceptionReason = new StringBuilder();

        // 时间异常
        if ("Late".equals(dingTalkRecord.getTimeResult())) {
            attendanceStatus = "1"; // 迟到
            exceptionReason.append("迟到;");
        } else if ("Early".equals(dingTalkRecord.getTimeResult())) {
            attendanceStatus = "2"; // 早退
            exceptionReason.append("早退;");
        }

        // 位置异常
        if ("Outside".equals(dingTalkRecord.getLocationResult())) {
            if ("0".equals(attendanceStatus)) {
                attendanceStatus = "5"; // 外勤
            }
            exceptionReason.append("外勤打卡;");
        }

        record.setAttendanceStatus(attendanceStatus);
        record.setExceptionReason(exceptionReason.length() > 0 ? exceptionReason.toString() : null);
        record.setIsDuplicate("0");

        return record;
    }

    /**
     * 检查考勤记录是否已存在
     */
    private boolean checkRecordExists(AttendanceRecord record) {
        Long count = attendanceRecordMapper.selectCount(
                Wrappers.lambdaQuery(AttendanceRecord.class)
                        .eq(AttendanceRecord::getEmployeeNo, record.getEmployeeNo())
                        .eq(AttendanceRecord::getClockTime, record.getClockTime())
                        .eq(AttendanceRecord::getClockType, record.getClockType()));
        return count != null && count > 0;
    }

    /**
     * 更新同步日志
     */
    private void updateSyncLog(SyncLog syncLog, String status, int totalCount,
            int successCount, int failCount, String failReason) {
        syncLog.setSyncStatus(status);
        syncLog.setTotalCount(totalCount);
        syncLog.setSuccessCount(successCount);
        syncLog.setFailCount(failCount);
        syncLog.setFailReason(failReason);
        syncLog.setSyncEndTime(LocalDateTime.now());
        syncLogMapper.updateById(syncLog);
    }


    /**
     * 同步OA请假/调休数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SyncLogVo syncLeaveRecords(LocalDate startDate, LocalDate endDate, boolean isManual) {
        if (!dingTalkProperties.getEnabled()) {
            log.warn("钉钉同步功能已禁用");
            return null;
        }

        // 创建同步日志
        SyncLog syncLog = new SyncLog();
        syncLog.setSyncSource("OA");
        syncLog.setSyncType(isManual ? "MANUAL" : "AUTO");
        syncLog.setSyncStartTime(LocalDateTime.now());
        syncLog.setSyncStatus("0"); // 进行中
        syncLogMapper.insert(syncLog);

        int totalCount = 0;
        int successCount = 0;
        int failCount = 0;
        StringBuilder failReason = new StringBuilder();

        try {
            // 获取所有在职和试用期员工
            List<HrEmployee> employees = employeeMapper.selectList(
                Wrappers.lambdaQuery(HrEmployee.class)
                    .in(HrEmployee::getEmployeeStatus, "1", "2") // 1在职 2试用期
                    .isNotNull(HrEmployee::getMobile));

            if (CollUtil.isEmpty(employees)) {
                log.warn("没有找到符合条件的员工");
                updateSyncLogFinish(syncLog, 0, 0, 0, "没有找到符合条件的员工", "2");
                return syncLogMapper.selectVoById(syncLog.getLogId());
            }

            // 构建员工手机号与钉钉UserID的映射
            Map<String, HrEmployee> userIdToEmployeeMap = new HashMap<>();
            List<String> dingTalkUserIds = new ArrayList<>();

            for (HrEmployee employee : employees) {
                String dingTalkUserId = dingTalkApiClient.getUserIdByMobile(employee.getMobile());
                if (org.apache.commons.lang3.StringUtils.isNotBlank(dingTalkUserId)) {
                    dingTalkUserIds.add(dingTalkUserId);
                    userIdToEmployeeMap.put(dingTalkUserId, employee);
                }
            }

            if (CollUtil.isEmpty(dingTalkUserIds)) {
                log.warn("没有匹配到任何钉钉用户ID");
                updateSyncLogFinish(syncLog, 0, 0, 0, "没有匹配到任何钉钉用户ID", "2");
                return syncLogMapper.selectVoById(syncLog.getLogId());
            }

            log.info("开始同步OA请假数据,日期范围:{} 至 {}, 匹配到{}个钉钉用户",
                startDate, endDate, dingTalkUserIds.size());

            // 获取钉钉请假记录
            List<DingTalkLeaveRecord> dingTalkRecords = dingTalkApiClient
                .getLeaveRecords(dingTalkUserIds, startDate, endDate);

            totalCount = dingTalkRecords.size();
            log.info("从钉钉获取到{}条请假记录", totalCount);

            // 处理每条请假记录
            for (DingTalkLeaveRecord dingTalkRecord : dingTalkRecords) {
                try {
                    HrEmployee employee = userIdToEmployeeMap.get(dingTalkRecord.getUserId());
                    if (employee == null) {
                        failCount++;
                        failReason.append("用户ID:").append(dingTalkRecord.getUserId())
                            .append(" 未匹配到员工;");
                        continue;
                    }

                    // 转换为系统请假记录
                    LeaveApplication leaveApplication = convertToLeaveApplication(dingTalkRecord, employee);

                    // 时间校验
                    if (leaveApplication.getStartTime().isAfter(leaveApplication.getEndTime())) {
                        failCount++;
                        failReason.append("员工:").append(employee.getEmployeeName())
                            .append(" 请假时间不合法(开始时间>结束时间);");
                        continue;
                    }

                    // 检查是否已存在(根据OA ID)
                    boolean exists = checkRecordExists(leaveApplication.getOaId());
                    if (exists) {
                        // 更新已有记录
                        LeaveApplication existingRecord = leaveApplicationMapper.selectOne(
                            Wrappers.lambdaQuery(LeaveApplication.class)
                                .eq(LeaveApplication::getOaId, leaveApplication.getOaId())
                                .last("LIMIT 1"));
                        if (existingRecord != null) {
                            leaveApplication.setApplicationId(existingRecord.getApplicationId());
                            leaveApplicationMapper.updateById(leaveApplication);
                        }
                    } else {
                        // 插入新记录
                        leaveApplicationMapper.insert(leaveApplication);
                    }

                    successCount++;
                } catch (Exception e) {
                    log.error("处理请假记录失败", e);
                    failCount++;
                    failReason.append("处理记录异常:").append(e.getMessage()).append(";");
                }
            }

            // 更新同步日志
            updateSyncLogFinish(syncLog, totalCount, successCount, failCount,
                failReason.length() > 0 ? failReason.toString() : null, "1");

            log.info("OA请假数据同步完成,总数:{},成功:{},失败:{}",
                totalCount, successCount, failCount);

        } catch (Exception e) {
            log.error("同步OA请假数据失败", e);
            updateSyncLogFinish(syncLog, totalCount, successCount, failCount,
                "同步异常:" + e.getMessage(), "2");
            throw new RuntimeException("同步OA请假数据失败", e);
        }

        return syncLogMapper.selectVoById(syncLog.getLogId());
    }

    /**
     * 转换钉钉请假记录为系统请假记录
     */
    private LeaveApplication convertToLeaveApplication(
        DingTalkLeaveRecord dingTalkRecord, HrEmployee employee) {

        LeaveApplication leaveApplication = new LeaveApplication();
        leaveApplication.setEmployeeNo(employee.getEmployeeNo());
        leaveApplication.setUserId(employee.getUserId());

        // 转换开始结束时间
        LocalDateTime startTime = LocalDateTime.ofInstant(
            Instant.ofEpochMilli(dingTalkRecord.getStartTime().getTime()),
            ZoneId.systemDefault());
        LocalDateTime endTime = LocalDateTime.ofInstant(
            Instant.ofEpochMilli(dingTalkRecord.getEndTime().getTime()),
            ZoneId.systemDefault());

        leaveApplication.setStartTime(startTime);
        leaveApplication.setEndTime(endTime);

        // 计算请假天数
        BigDecimal leaveDays = calculateLeaveDays(dingTalkRecord);
        leaveApplication.setLeaveDays(leaveDays);

        // 映射请假类型(根据钉钉的tagNames)
        String leaveType = mapLeaveType(dingTalkRecord.getTagNames());
        leaveApplication.setLeaveType(leaveType);

        // 映射审批状态
        String approvalStatus = mapApprovalStatus(dingTalkRecord.getApproveState());
        leaveApplication.setApprovalStatus(approvalStatus);

        leaveApplication.setSource("OA");
        leaveApplication.setOaId(dingTalkRecord.getBizId());
        leaveApplication.setStatus("0"); // 正常
        leaveApplication.setLeaveReason("OA系统同步");

        return leaveApplication;
    }

    /**
     * 计算请假天数
     */
    private BigDecimal calculateLeaveDays(DingTalkLeaveRecord record) {
        try {
            String durationPercent = record.getDurationPercent();
            if (org.apache.commons.lang3.StringUtils.isBlank(durationPercent)) {
                return BigDecimal.ZERO;
            }

            // durationPercent是百分比,如"0.5"表示0.5天
            BigDecimal days = new BigDecimal(durationPercent);

            // 如果是按小时计算,需要转换为天
            if ("percent_hour".equals(record.getDurationUnit())) {
                days = days.divide(new BigDecimal("8"), 1, RoundingMode.HALF_UP);
            }

            return days.setScale(1, RoundingMode.HALF_UP);
        } catch (Exception e) {
            log.error("计算请假天数失败", e);
            return BigDecimal.ZERO;
        }
    }

    /**
     * 映射请假类型
     */
    private String mapLeaveType(String tagNames) {
        if (org.apache.commons.lang3.StringUtils.isBlank(tagNames)) {
            return "1"; // 默认事假
        }

        // 根据请假类型名称映射到系统类型
        if (tagNames.contains("事假")) {
            return "1";
        } else if (tagNames.contains("病假")) {
            return "2";
        } else if (tagNames.contains("年假")) {
            return "3";
        } else if (tagNames.contains("调休")) {
            return "4";
        } else if (tagNames.contains("婚假")) {
            return "5";
        } else if (tagNames.contains("产假")) {
            return "6";
        } else if (tagNames.contains("陪产假")) {
            return "7";
        } else if (tagNames.contains("丧假")) {
            return "8";
        } else {
            return "1"; // 默认事假
        }
    }

    /**
     * 映射审批状态
     */
    private String mapApprovalStatus(String approveState) {
        if (org.apache.commons.lang3.StringUtils.isBlank(approveState)) {
            return "0"; // 待审批
        }

        // NEW新建 -> 0待审批
        // RUNNING审批中 -> 1审批中
        // COMPLETED完成审批 -> 2已通过
        // TERMINATED已终止 -> 3已拒绝
        return switch (approveState) {
            case "RUNNING" -> "1";
            case "COMPLETED" -> "2";
            case "TERMINATED" -> "3";
            default -> "0";
        };
    }

    /**
     * 检查记录是否已存在
     */
    private boolean checkRecordExists(String oaId) {
        if (org.apache.commons.lang3.StringUtils.isBlank(oaId)) {
            return false;
        }
        Long count = leaveApplicationMapper.selectCount(
            Wrappers.lambdaQuery(LeaveApplication.class)
                .eq(LeaveApplication::getOaId, oaId));
        return count != null && count > 0;
    }

    /**
     * 更新同步日志完成状态
     */
    private void updateSyncLogFinish(SyncLog syncLog, int totalCount, int successCount,
                                     int failCount, String failReason, String status) {
        syncLog.setSyncEndTime(LocalDateTime.now());
        syncLog.setTotalCount(totalCount);
        syncLog.setSuccessCount(successCount);
        syncLog.setFailCount(failCount);
        syncLog.setFailReason(failReason);
        syncLog.setSyncStatus(status);
        syncLogMapper.updateById(syncLog);
    }

    /**
     * 自动同步(定时任务调用)
     */
    @Override
    public void autoLeaveSync() {
        // 默认同步前一天的数据
        LocalDate yesterday = LocalDate.now().minusDays(1);
        syncLeaveRecords(yesterday, yesterday, false);
    }


    /**
     * 重新同步失败的记录
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean retryLeaveSyncByLogId(Long logId) {
        SyncLog oldLog = syncLogMapper.selectById(logId);
        if (oldLog == null) {
            throw new RuntimeException("同步日志不存在");
        }

        // 提取原始同步的日期范围
        LocalDate yesterday = LocalDate.now().minusDays(1);
        syncLeaveRecords(yesterday, yesterday, true);

        return true;
    }
}
